package com.jmb.api.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jmb.api.common.exception.ParseException;
import com.jmb.api.common.exception.SqlException;
import com.jmb.api.common.utils.ExceptionUtil;
import com.jmb.api.common.utils.JsonUtil;
import com.jmb.api.common.utils.NoUtil;
import com.jmb.api.common.utils.RedisUtil;
import com.jmb.api.core.parser.Parser;
import com.jmb.api.mapper.ApiCaseSuiteCaseRefMapper;
import com.jmb.api.mapper.ApiCaseSuiteMapper;
import com.jmb.api.mapper.ApiTestCaseExecuteLogMapper;
import com.jmb.api.model.entity.ApiCaseSuiteCaseRef;
import com.jmb.api.model.entity.ApiCaseSuiteLog;
import com.jmb.api.model.entity.ExecuteInterfaceCaseParam;
import com.jmb.api.model.to.api.suite.ApiCaseSuiteCaseRefTO;
import com.jmb.api.model.to.api.suite.ApiCaseSuiteInfoVO;
import com.jmb.api.model.to.api.suite.ApiCaseSuiteProcessorTO;
import com.jmb.api.model.vo.api.log.ApiTestCaseExecuteLogVO;
import com.jmb.api.model.vo.api.suite.ApiCaseSuiteCaseRefVO;
import com.jmb.api.model.vo.api.suite.ApiCaseSuiteProcessorVO;
import com.jmb.api.service.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

/**
* @author jinbo
* @description 针对表【api_case_suite_case_ref】的数据库操作Service实现
* @createDate 2025-04-08 13:53:56
*/
@Slf4j
@Service
@Transactional
public class ApiCaseSuiteCaseRefServiceImpl extends ServiceImpl<ApiCaseSuiteCaseRefMapper, ApiCaseSuiteCaseRef>
    implements ApiCaseSuiteCaseRefService{
    @Autowired
    private ApiCaseSuiteCaseRefMapper apiCaseSuiteCaseRefMapper;
    @Autowired
    private ApiTestCaseExecuteLogMapper executeLogMapper;
    @Autowired
    private ApiCaseSuiteMapper caseSuiteMapper;
    @Autowired
    private ApiCaseSuiteLogService caseSuiteLogService;
    @Autowired
    private ApiTestCaseExecuteLogService executeLogService;
    @Autowired
    private ApiCaseSuiteProcessorService caseSuiteProcessorService;
    @Autowired
    private ApiTestCaseService caseService;
    @Autowired
    private Parser parser;
    @Autowired
    private RedisUtil redisUtil;
    /**
     * 测试套件新增用例
     * @param caseSuiteCaseRef 用例集合
     */
    @Override
    public void saveSuiteCaseRef(List<ApiCaseSuiteCaseRef> caseSuiteCaseRef) {
        ArrayList<ApiCaseSuiteCaseRef> list = new ArrayList<>();
        caseSuiteCaseRef.forEach(apiCaseSuiteCaseRef -> {
            ApiCaseSuiteCaseRefTO apiCaseSuiteCaseRefTO = new ApiCaseSuiteCaseRefTO();
            BeanUtils.copyProperties(apiCaseSuiteCaseRef,apiCaseSuiteCaseRefTO);
            List<ApiCaseSuiteCaseRefVO> caseRefVoList = apiCaseSuiteCaseRefMapper.selectSuiteList(apiCaseSuiteCaseRefTO);
            if(caseRefVoList.isEmpty()){
                list.add(apiCaseSuiteCaseRef);
            }
        });
        if (!list.isEmpty()){
            this.saveBatch(list);
        }
    }

    /**
     * 测试套件新增用例单条
     * @param caseSuiteCaseRef 用例对象
     */
    @Override
    public void saveSuiteCaseRefSingle(ApiCaseSuiteCaseRef caseSuiteCaseRef) {
        this.save(caseSuiteCaseRef);
    }

    @Override
    public void editSuiteCaseRef(ApiCaseSuiteCaseRef caseSuiteCaseRef) {
        LambdaUpdateWrapper<ApiCaseSuiteCaseRef> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(ApiCaseSuiteCaseRef::getId,caseSuiteCaseRef.getId());
        this.update(updateWrapper);
    }

    @Override
    public void removeSuiteCaseRef(Integer id) {
        this.removeById(id);
    }

    @Override
    public void removeSuiteCaseRefByObject(ApiCaseSuiteCaseRef caseSuiteCaseRef) {
        LambdaQueryWrapper<ApiCaseSuiteCaseRef> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ApiCaseSuiteCaseRef::getSuiteId,caseSuiteCaseRef.getSuiteId())
                .eq(ApiCaseSuiteCaseRef::getCaseId,caseSuiteCaseRef.getCaseId());
        this.remove(queryWrapper);
    }

    /**
     * 分页查询
     * @param page 传入分页页码对象
     * @param caseSuiteCaseRefTO 查询条件
     * @return 分页后的数据
     */
    @Override
    public IPage<ApiCaseSuiteCaseRefVO> findSuiteCaseRefList(IPage<ApiCaseSuiteCaseRefVO> page,ApiCaseSuiteCaseRefTO caseSuiteCaseRefTO) {
        return apiCaseSuiteCaseRefMapper.selectSuiteCaseList(page,caseSuiteCaseRefTO);
    }

    /**
     * 查询所有
     * @param caseSuiteCaseRefTO 查询条件
     * @return 所有数据，不分页的
     */
    @Override
    public List<ApiCaseSuiteCaseRefVO> findAllSuiteCaseRef(ApiCaseSuiteCaseRefTO caseSuiteCaseRefTO) {
        return apiCaseSuiteCaseRefMapper.selectSuiteList(caseSuiteCaseRefTO);
    }

    /**
     * 执行测试套件
     * @param suiteId 测试套件编号
     * @param executor 执行者
     * @return 测试套件执行日志编号
     */
    @Override
    public String executeSuiteCaseById(Integer suiteId, String executor) throws ParseException {
        // 获取测试套件下所有测试用例
        ApiCaseSuiteCaseRefTO apiCaseSuiteCaseRefTO = new ApiCaseSuiteCaseRefTO();
        apiCaseSuiteCaseRefTO.setSuiteId(suiteId);
        log.info("执行测试套件开始，测试套件编号：{},传入的对象是{}",suiteId,apiCaseSuiteCaseRefTO);
        List<ApiCaseSuiteCaseRefVO> caseRefVOList = apiCaseSuiteCaseRefMapper.selectSuiteList(apiCaseSuiteCaseRefTO);
        // 判断测试套件执行方式 0并行1串行
        ApiCaseSuiteInfoVO caseSuiteInfoVO = caseSuiteMapper.selectApiCaseSuiteInfoById(suiteId);
        Integer executeType = caseSuiteInfoVO.getExecuteType();
        Integer runDev = caseSuiteInfoVO.getRunDev();
        AtomicBoolean isRetry = new AtomicBoolean(caseSuiteInfoVO.getIsRetry() == 0);
        Date startTime = new Date();
        long begin = System.currentTimeMillis();
        String suiteLogNo = NoUtil.genStabilityLogNo();
        String suiteLogDetailNo = NoUtil.genSuiteLogDetailNo(suiteLogNo);
        String suiteLogProgressNo = NoUtil.genSuiteLogProgressNo(suiteLogNo);
        AtomicInteger totalRunCase = new AtomicInteger();
        Integer totalCase = caseRefVOList.size();
        AtomicInteger totalSkip = new AtomicInteger();
        AtomicInteger totalSuccess = new AtomicInteger();
        AtomicInteger totalFailed = new AtomicInteger();
        AtomicInteger totalError = new AtomicInteger();
        AtomicInteger totalRetry = new AtomicInteger();
        // 写入记录，在正式调度之前，把进度重置进行中
        ApiCaseSuiteLog initDO = new ApiCaseSuiteLog();
        initDO.setSuiteId(suiteId);
        initDO.setSuiteLogNo(suiteLogNo);
        initDO.setTotalCase(totalCase);
        initDO.setStartTime(startTime);
        initDO.setExecuteType(executeType);
        initDO.setRunDev(runDev);
        initDO.setExecutor(executor);
        initDO.setIsRetry(caseSuiteInfoVO.getIsRetry());
        initDO.setProgress(0);
        initDO.setPercentage(0);
        ApiCaseSuiteLog saveIfSuiteLog = caseSuiteLogService.saveIfSuiteLog(initDO);
        log.info("获取到的是{}",saveIfSuiteLog);
        Integer id = saveIfSuiteLog.getId();
        log.info("获取到的id是{}",id);
        // 获取测试套件前置处理器
        log.info("-----------------------开始前置处理器依赖执行-----------------------");
        try {
            this.executeRely(suiteId, 0, saveIfSuiteLog.getSuiteLogNo());
        }catch (Exception e){
            this.updateProgressFailed(id);
            log.error(ExceptionUtil.msg(e));
            log.error("------------------前置处理器依赖执行失败-------------------");
            throw new ParseException("前置处理器依赖执行失败");
        }
        log.info("-----------------------前置处理器依赖执行完成-----------------------");
        HashMap globalHeaders;
        HashMap globalParams;
        HashMap globalData;
        try {
            globalHeaders = this.globalHeaders(suiteId, suiteLogDetailNo);
            globalParams = this.globalParams(suiteId, suiteLogDetailNo);
            globalData = this.globalData(suiteId, suiteLogDetailNo);

        }catch (Exception e){
            this.updateProgressFailed(saveIfSuiteLog.getId());
            this.updateProgressPercentage(saveIfSuiteLog.getId(), 0);
            log.error(ExceptionUtil.msg(e));
            log.error("打印出错问题日志",e);
            throw new ParseException("前置处理器执行失败");
        }
        if (executeType == 0){
            // 异步方法
            log.info("-----------------------开始并行执行测试套件，suiteId={}--------------------------",suiteId);
            caseRefVOList.parallelStream().forEach(suiteCase -> {
                Integer caseId = suiteCase.getCaseId();
                boolean doRetryFlag = false;
                if (isRetry.get()){
                    doRetryFlag = true;
                }
               try {
                   // 禁用
                   if (suiteCase.getCaseStatus() == 1){
                       totalSkip.getAndIncrement();
                   }else {
                       Integer executeLogId = caseService.executeCase(new ExecuteInterfaceCaseParam(
                               caseId, executor, suiteLogNo, NoUtil.genChainNo(), suiteId, 1, suiteLogDetailNo,
                               globalHeaders, globalParams, globalData, 2, null, doRetryFlag));
                       ApiTestCaseExecuteLogVO caseExecuteLogVO = executeLogService.findExecute(executeLogId);
                       Integer status = caseExecuteLogVO.getStatus();
                       if (status == 0){
                           totalSuccess.getAndIncrement();
                       }
                       else if (status ==1){
                           // 失败重试
                           if (doRetryFlag){
                               totalRetry.getAndIncrement();
                               Integer executed = caseService.executeCase(new ExecuteInterfaceCaseParam(
                                       caseId, executor, suiteLogNo, NoUtil.genChainNo(), suiteId, 0, suiteLogDetailNo,
                                       globalHeaders, globalParams, globalData, 2, null, false
                               ));
                               ApiTestCaseExecuteLogVO retryVo = executeLogMapper.selectExecute(executed);
                               Integer retryStatus = retryVo.getStatus();
                               if (retryStatus == 0){
                                   totalSuccess.getAndIncrement();
                               }else if (retryStatus == 1){
                                   totalFailed.getAndIncrement();
                               }else {
                                   totalError.getAndIncrement();
                               }
                           }
                           else {
                               totalFailed.getAndIncrement();
                           }
                       }
                       else {
                           totalError.getAndIncrement();
                       }
                       totalRunCase.getAndIncrement();
                   }
               }catch (Exception e){
                   this.updateProgressFailed(id);
                   int precent = this.updateProgressCache(suiteLogProgressNo, caseRefVOList.size(), totalRunCase, totalSkip);
                   this.updateProgressPercentage(id, precent);
                   log.error("并行执行测试套件出现异常，errorMsg={}",ExceptionUtil.msg(e));
                   throw new RuntimeException(e.getMessage());
               }
                int total = caseRefVOList.size();
               this.updateProgressCache(suiteLogProgressNo,total,totalRunCase,totalSkip);
            });
        }
        else {
            // 同步
            log.info("-----------------------------开始串行执行测试套件，suiteId={}------------------",suiteId);
            for (ApiCaseSuiteCaseRefVO suiteCase : caseRefVOList){
                Integer caseId = suiteCase.getCaseId();
                boolean doRetryFlag = isRetry.get();
                // 禁用
                if (suiteCase.getCaseStatus() ==1){
                    totalSkip.getAndIncrement();
                }else {
                    Integer executeLogId = caseService.executeCase(new ExecuteInterfaceCaseParam(
                            caseId, executor, suiteLogNo, NoUtil.genChainNo(), suiteId, 1, suiteLogDetailNo,
                            globalHeaders, globalParams, globalData, 2, null, false
                    ));
                    ApiTestCaseExecuteLogVO apiTestCaseExecuteLogVO = executeLogMapper.selectExecute(executeLogId);
                    Integer status = apiTestCaseExecuteLogVO.getStatus();
                    if (status == 0){
                        totalSuccess.getAndIncrement();
                    }
                    else if (status ==1){
                        if (doRetryFlag){
                            totalRetry.getAndIncrement();
                            Integer retryExecuteLogId = caseService.executeCase(new ExecuteInterfaceCaseParam(
                                    caseId, executor, suiteLogNo, NoUtil.genChainNo(), suiteId, 0, suiteLogDetailNo,
                                    globalHeaders, globalParams, globalData, 2, null, false
                            ));
                            ApiTestCaseExecuteLogVO retryVO = executeLogMapper.selectExecute(retryExecuteLogId);
                            Integer retryStatus = retryVO.getStatus();
                            if (retryStatus == 0){
                                totalSuccess.getAndIncrement();
                            }
                            else if (retryStatus == 1){
                                totalFailed.getAndIncrement();
                            }
                            else {
                                totalError.getAndIncrement();
                            }
                        }else {
                            totalFailed.getAndIncrement();
                        }
                    }else {
                        totalError.getAndIncrement();
                    }
                    totalRunCase.getAndIncrement();
                }
                int total = caseRefVOList.size();
                this.updateProgressCache(suiteLogProgressNo,total,totalRunCase,totalSkip);
            }

        }
        // 修改测试套件执行日志表
        long end = System.currentTimeMillis();
        Date endTime = new Date();
        ApiCaseSuiteLog apiCaseSuiteLog = new ApiCaseSuiteLog();
        apiCaseSuiteLog.setId(saveIfSuiteLog.getId()); //新增自增key
        apiCaseSuiteLog.setSuiteId(suiteId);
        apiCaseSuiteLog.setSuiteLogNo(suiteLogNo);
        apiCaseSuiteLog.setRunTime((end - begin));
        apiCaseSuiteLog.setTotalSuccess(totalSuccess.intValue());
        apiCaseSuiteLog.setTotalFailed(totalFailed.intValue());
        apiCaseSuiteLog.setTotalError(totalError.intValue());
        apiCaseSuiteLog.setTotalSkip(totalSkip.intValue());
        apiCaseSuiteLog.setTotalRunCase(totalRunCase.intValue());
        apiCaseSuiteLog.setTotalCase(totalCase);
        apiCaseSuiteLog.setStartTime(startTime);
        apiCaseSuiteLog.setEndTime(endTime);
        apiCaseSuiteLog.setExecuteType(executeType);
        apiCaseSuiteLog.setRunDev(runDev);
        apiCaseSuiteLog.setExecutor(executor);
        apiCaseSuiteLog.setIsRetry(caseSuiteInfoVO.getIsRetry());
        apiCaseSuiteLog.setProgress(1);
        apiCaseSuiteLog.setPercentage(100);
        log.info("执行测试套件，totalCase={},totalSuccess={}, totalFailed={}, totalError={}, totalSkip={}, 运行环境={}, 执行方式={}",totalCase,totalSuccess.intValue(), totalFailed.intValue(), totalError.intValue(), totalSkip.intValue(), runDev,executeType);
        // 仅开启失败重跑才写入字段， 否则什么也不如写入
        if (isRetry.get()){
            apiCaseSuiteLog.setTotalRetry(totalRetry.intValue());
        }else {
            apiCaseSuiteLog.setTotalRetry(null);
        }
        caseSuiteLogService.modifyIfSuiteLog(apiCaseSuiteLog);
        log.info("写入测试套件执行日志表，suiteLogNo={}，success={}，failed={}，error={}, skip={}, 运行环境={}, 执行方式={}",
                suiteLogNo, totalSuccess.intValue(), totalFailed.intValue(), totalError.intValue()
                , totalSkip.intValue(), executeType, runDev);
        // 获取测试套件后置处理器
        log.info("----------------------开始后置处理器依赖执行--------------------------");
        try {
            this.executeRely(suiteId,1,suiteLogDetailNo);

        }catch (Exception e){
            this.updateProgressFailed(id);
            redisUtil.set(suiteLogProgressNo,99,60*60*12);
            this.updateProgressPercentage(id,99);
            log.error(ExceptionUtil.msg(e));
            log.error("执行后置处理器异常",e);
            throw new ParseException("后置处理器执行失败");
        }
        log.info("----------------------结束后置处理器依赖执行--------------------------");
        log.info("执行测试套件完成，totalCase={},totalSuccess={}, totalFailed={}, totalError={}, totalSkip={}, 运行环境={}, 执行方式={}",totalCase,totalSuccess.intValue(), totalFailed.intValue(), totalError.intValue(), totalSkip.intValue(), runDev,executeType);

        // 删除后置处理器缓存
        redisUtil.del(suiteLogDetailNo);
        // 删除进度缓存‘
        redisUtil.del(suiteLogProgressNo);

        return suiteLogNo;

    }

    @Override
    public Integer executeCaseInSuite(Integer suiteId, Integer caseId, String executor) {
        return 0;
    }

    /**
     * 执行依赖
     * @param suiteId suiteId
     * @param processorType processorType 0前置处理器1后置处理器
     * @param suiteLogDetailNo suiteLogDetailNo
     * @throws ParseException ParseException
     * @throws SqlException SqlException
     */
    private void executeRely(Integer suiteId, Integer processorType, String suiteLogDetailNo) throws ParseException, SqlException {
        ApiCaseSuiteProcessorTO cacheParameterTO = new ApiCaseSuiteProcessorTO();
        cacheParameterTO.setProcessorType(processorType);
        cacheParameterTO.setSuiteId(suiteId);
        cacheParameterTO.setType(0);
        List<ApiCaseSuiteProcessorVO> processorList = caseSuiteProcessorService.findAllApiCaseSuiteProcessorList(cacheParameterTO);
        if (!processorList.isEmpty()) {
            ApiCaseSuiteProcessorVO apiCaseSuiteProcessorVO = processorList.get(0);
            // 获取依赖表达式
            String value = apiCaseSuiteProcessorVO.getValue();
            parser.parseDependency(value, NoUtil.genChainNo(), suiteId,  1, suiteLogDetailNo,
                    null, null, null, null);
        }
    }
    /**
     * 获取global headers hashMap
     * @param suiteId suiteId
     * @param suiteLogDetailNo suiteLogDetailNo
     * @return 获取global headers hashMap
     * @throws ParseException ParseException
     * @throws SqlException SqlException
     */
    private HashMap globalHeaders(Integer suiteId, String suiteLogDetailNo) throws  ParseException, SqlException {
        return JsonUtil.jsonString2HashMap(this.getPreProcessor(suiteId,1, suiteLogDetailNo));
    }

    /**
     * 获取global params hashMap
     * @param suiteId suiteId
     * @param suiteLogDetailNo suiteLogDetailNo
     * @return 获取global headers hashMap
     * @throws ParseException ParseException
     * @throws SqlException SqlException
     */
    private HashMap globalParams(Integer suiteId, String suiteLogDetailNo) throws  ParseException, SqlException {
        return JsonUtil.jsonString2HashMap(this.getPreProcessor(suiteId, 2, suiteLogDetailNo));
    }

    /**
     * 获取global data hashMap
     * @param suiteId suiteId
     * @param suiteLogDetailNo suiteLogDetailNo
     * @return 获取global headers hashMap
     * @throws ParseException ParseException
     * @throws SqlException SqlException
     */
    private HashMap globalData(Integer suiteId, String suiteLogDetailNo) throws ParseException, SqlException, ParseException {
        return JsonUtil.jsonString2HashMap(this.getPreProcessor(suiteId,  3, suiteLogDetailNo));
    }
    /**
     * 获取前置处理器你
     * @param suiteId suiteId
     * @param type type
     * @param suiteLogDetailNo suiteLogDetailNo
     * @return value

     * @throws ParseException ParseException
     * @throws SqlException SqlException
     */
    private String getPreProcessor(Integer suiteId, Integer type, String suiteLogDetailNo) throws ParseException, SqlException {
        ApiCaseSuiteProcessorTO apiCaseSuiteProcessorTO = new ApiCaseSuiteProcessorTO();
        apiCaseSuiteProcessorTO.setProcessorType(0);
        apiCaseSuiteProcessorTO.setSuiteId(suiteId);
        apiCaseSuiteProcessorTO.setType(type);
        List<ApiCaseSuiteProcessorVO> processorList = caseSuiteProcessorService.findAllApiCaseSuiteProcessorList(apiCaseSuiteProcessorTO);
        if (!processorList.isEmpty()) {
            ApiCaseSuiteProcessorVO apiCaseSuiteProcessorVO = processorList.get(0);
            // 获取0执行依赖1公共头2公共params3公共data
            String value = apiCaseSuiteProcessorVO.getValue();
            if (value != null) {
                value = parser.parseDependency(value, NoUtil.genChainNo(), suiteId,  1,
                        suiteLogDetailNo,null, null, null, null);
            }
            return value;
        }
        return null;
    }
    /**
     * 修改测试套件执行日志状态
     * @param suiteLogId 测试套件日志编号
     */
    private void updateProgressFailed(Integer suiteLogId) {
        ApiCaseSuiteLog logDO = new ApiCaseSuiteLog();
        logDO.setId(suiteLogId);
        logDO.setProgress(2);
        caseSuiteLogService.modifyIfSuiteLog(logDO);
    }

    /**
     * 修改测试套件执行进度百分比
     * @param suiteLogId 测试套件日志编号
     * @param percentage 百分比
     */
    private void updateProgressPercentage(Integer suiteLogId, Integer percentage) {
        ApiCaseSuiteLog logDO = new ApiCaseSuiteLog();
        logDO.setId(suiteLogId);
        logDO.setPercentage(percentage);
        caseSuiteLogService.modifyIfSuiteLog(logDO);
    }

    /**
     * 缓存进度进度
     * @param suiteLogProgressNo suiteLogProgressNo
     * @param total 总用例
     * @param totalRunCase 总运行
     * @param totalSkip 总跳过
     */
    private int updateProgressCache(String suiteLogProgressNo, Integer total, AtomicInteger totalRunCase, AtomicInteger totalSkip) {
        float run = totalRunCase.floatValue() + totalSkip.floatValue();
        if (total == 0 || run == 0) {
            redisUtil.set(suiteLogProgressNo, 0, 60*60*12);
            return 0;
        } else {
            int rate = (int) (run/total*100);
            redisUtil.set(suiteLogProgressNo, rate,60*60*12);
            return rate;
        }
    }
}




